package com.mikufufu.blog.aop;

import com.alibaba.fastjson2.JSON;
import com.mikufufu.blog.common.annotation.Account;
import com.mikufufu.blog.common.annotation.OperationLog;
import com.mikufufu.blog.common.constant.HttpStatus;
import com.mikufufu.blog.common.entity.AjaxResult;
import com.mikufufu.blog.common.enums.OperationType;
import com.mikufufu.blog.model.entity.OperateLog;
import com.mikufufu.blog.model.event.LogEvent;
import com.mikufufu.blog.security.utils.AuthUtils;
import com.mikufufu.blog.utils.IpUtil;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.core.annotation.AnnotationUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Slf4j
@Aspect
@Component
public class LogAspect {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Pointcut("@annotation(com.mikufufu.blog.common.annotation.OperationLog)")
    public void operationLogPointCut() {
    }

    @AfterReturning(value = "operationLogPointCut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String[] paramNames = signature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        OperationLog operationLog = method.getAnnotation(OperationLog.class);
        OperateLog operateLog = new OperateLog();
        setOperateLog(operationLog,method,paramNames,args,operateLog);
        if (Objects.nonNull(result) && result instanceof AjaxResult){
            AjaxResult<?> ajaxResult = (AjaxResult<?>) result;
            if (ajaxResult.getCode() == HttpStatus.SUCCESS){
                operateLog.setStatus(1);
            }else{
                OperationType type = operationLog.type();
                if (type == OperationType.LOGIN){
                    return;
                }
                operateLog.setStatus(0);
                operateLog.setErrorMessage(ajaxResult.getMsg());
            }
        }
        eventPublisher.publishEvent(new LogEvent(operateLog));

    }

    /**
     * 设置操作日志
     * @param operateLog 操作日志
     */
    private void setOperateLog(OperationLog operationLog, Method method, String[] paramNames,Object[] args,OperateLog operateLog) {
        HttpServletRequest request = getRequest();
        if (request == null){
            throw new RuntimeException("获取请求失败");
        }
        OperationType type = operationLog.type();
        operateLog.setModule(operationLog.module().getType());
        operateLog.setUrl(request.getRequestURI());
        setParam(operateLog, method,paramNames,args);
        if (method.isAnnotationPresent(ApiOperation.class)){
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            operateLog.setMethodName(apiOperation.value());
        }
        operateLog.setOperator(AuthUtils.getUsername());
        if (OperationType.LOGIN.equals(type)){
            setLoginLog(operateLog,args);
        }
        operateLog.setIp(IpUtil.getIpAddress(request));
        operateLog.setOperateType(type.getType());
        operateLog.setOperateTime(new Date());
        operateLog.setRemark(operationLog.description());
    }


    private void setLoginLog(OperateLog operateLog, Object[] args) {
        for(Object arg : args){
            Field[] fields = arg.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Account.class)){
                    try {
                        field.setAccessible(true);
                        operateLog.setOperator((String) field.get(arg));
                        return;
                    }catch (Exception e){
                        log.error("获取参数失败",e);
                        operateLog.setOperator("");
                        return;
                    }
                }
            }
        }
    }

    /**
     * 设置参数
     * @param operateLog 操作日志
     * @param method 方法
     * @param paramNames 参数名
     * @param args 参数值
     */
    private void setParam(OperateLog operateLog,Method method,String[] paramNames, Object[] args) {
        // 如果参数为空，则直接返回
        if (args.length == 0){
            return;
        }
        // 如果只有一个参数，且是RequestBody注解，则直接返回
        if (args.length == 1 && AnnotationUtils.findAnnotation(method.getParameters()[0], RequestBody.class) != null){
            operateLog.setParams(JSON.toJSONString(args[0]));
            return;
        }
        // 如果只有一个参数，且是RequestBody注解，则直接返回
//        if (args.length == 1 && method.getParameterAnnotations()[0][0] instanceof RequestBody){
//            operateLog.setParams(JSON.toJSONString(args[0]));
//            return;
//        }
        // 遍历参数，将参数名和参数值存入map中
        Map<String, Object> params = new HashMap<>();
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof MultipartFile) {
                continue;
            }
            if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse) {
                continue;
            }
            if (args[i] == null || args[i] instanceof byte[]) {
                continue;
            }
            params.put(paramNames[i], args[i]);
        }
        operateLog.setParams(JSON.toJSONString(params));
    }

    private HttpServletRequest getRequest() {
        // 从当前请求中获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            return ((ServletRequestAttributes) requestAttributes).getRequest();
        }
        return null;
    }
}
